#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <time.h>
#include <errno.h>

#define TRUE 1
#define FALSE 0

extern int    errno;
extern int    optind;
extern char * optarg;

float drop_rate = 0.0f;
int dropping = FALSE;
float bit_error_rate = 0.0f;
int bit_erroring = FALSE;
int delay = 0;

char Usage[] = "\
Usage: traffic_forwarder [-options] listen_port to_host to_port\n\
Options:\n\
   -b ## Bit error percentage - Likelihood of a bit being changed\n\
                                during transport.\n\
   -p ## Dropped packet percentage - Likelihood of a packet to be\n\
                                     dropped during transmission\n";
                                     
        

void leave(const char *msg)
{
    puts(msg);
    exit(1);
}

void usage(const char *msg)
{
   puts(msg);
   puts(Usage);
   exit(3);
}

void error(const char *msg)
{
    perror(msg);
    exit(2);
}

int main(int argc, char* argv[])
{
   const int BUFFER_LENGTH = 50000;
   char buffer[BUFFER_LENGTH];
   int sockfd;
   int nbytes;
   char c,*ctemp;
   long listen_port,to_port; //longs because of input method
   struct timeval sleep_timer;
   struct sockaddr_in this_addr,from_addr,to_addr,recv_addr;
   int sockaddr_in_size;
   struct hostent *to_host;
   
   while ((c = getopt(argc,argv,"p:b:d:")) != -1)
   {
      switch (c)
      {
         case 'p':
            dropping = TRUE;
            drop_rate = ((float)strtod(optarg,&ctemp))/100.0f;
            if (drop_rate < -0.000001 || drop_rate > 1.000001)//give inprecise floats some room to breath
               usage("Invalid package drop percentage");
            break;
         case 'b':
            bit_erroring = TRUE;
            bit_error_rate = ((float)strtod(optarg,&ctemp))/100.0f;
            if (bit_error_rate < -0.000001 || bit_error_rate > 1.000001) //give inprecise floats some room to breath
               usage("Invalid bit error percentage");
            break;
         case 'd':
            delay = strtol(optarg,&ctemp,0);
            if (*ctemp != NULL || delay < 0)
               usage("Invalid delay");
            break;
         default:
            usage("Invalid options chosen");
      }
   }
   
   if (argc - optind < 3)
   {
      usage("Invalid amount of arguments");
   }
   else 
   {
      listen_port = strtol(argv[optind],&ctemp,0);
      if (*ctemp != NULL || listen_port > 65535 || listen_port < 1024)
         leave("Invalid listen port number use 1024-65535");
      
      to_host = gethostbyname(argv[optind+1]);
      if (to_host == NULL)
         error("ERROR, no such host");
      
      to_port = strtol(argv[optind+2],&ctemp,0);
      if (*ctemp != NULL || to_port > 65535 || to_port < 1024)
         leave("Invalid sending port number use 1024-65535");
   }
   
   //set up socket
   if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
      error("ERROR opening socket");
   
   //set up server listening addr
   bzero((char *) &this_addr, sizeof(this_addr));
   this_addr.sin_family = AF_INET;
   this_addr.sin_addr.s_addr = htonl(INADDR_ANY);
   this_addr.sin_port = htons(listen_port);
   
   to_addr.sin_family = AF_INET;
   bcopy((char *) to_host->h_addr, (char *) &to_addr.sin_addr.s_addr,to_host->h_length);
   to_addr.sin_port = htons(to_port);
   
   //bind to the socket
   if (bind(sockfd, (struct sockaddr *) &this_addr, sizeof(this_addr)) < 0)
      error("ERROR on binding");
   
   //initialize values before running
   sockaddr_in_size = sizeof(to_addr);
   sleep_timer.tv_sec = delay / 1000000L;
   sleep_timer.tv_usec = delay % 1000000L;
   
   srand(time(NULL));
   puts("Running");
   while (1)
   {      
      //wait for information on the socket and then retrieve it
      //currently only receives from one place and forwards it and then returns it to that place when done
      //if you were to contact from another original host you will then receive all return packages to the 
      //new host
      if ((nbytes = recvfrom(sockfd,buffer,BUFFER_LENGTH,0,(struct sockaddr *) &recv_addr, &sockaddr_in_size)) < 0)
         error("ERROR reading from socket");
      
      //puts("Received Message");
      
      if (dropping)
         if (rand()/(RAND_MAX + 1.0) < drop_rate)
            continue;           
         
      //check if this is coming from where we are forwarding to or not
      //if so return it to the original sender of packets, otherwise send it to
      //designated forward receiver.
      if (recv_addr.sin_addr.s_addr == to_addr.sin_addr.s_addr && recv_addr.sin_port == to_addr.sin_port)
      {
         if ((nbytes = sendto(sockfd,buffer,nbytes,0,(struct sockaddr *) &from_addr, sizeof(from_addr))) < 0)
            error("ERROR writing to socket");   
      }
      else 
      {
         //only introducing errors on trgen->trsink messages as the assignment said to assume
         //that acknowledgement are error free.
         if (bit_erroring)
            if ((rand()/(RAND_MAX + 1.0)) < (bit_error_rate * nbytes * 8))
               buffer[4] += 1; //don't want to mess up the message id - that will screw up our ability to identify it.
         
         if (delay)
            select(0, NULL,NULL,NULL, &sleep_timer); 
         
         if ((nbytes = sendto(sockfd,buffer,nbytes,0,(struct sockaddr *) &to_addr, sizeof(to_addr))) < 0)
            error("ERROR writing to socket");
         
         //since its not coming from where we are forwarding to, we will now
         //save off data of who is sending us packets.  Could just be done once
         //on start up to allow only one client to work per trfwd, but this way 
         //will allow you to just use it multiple times in a run but just one 
         //at a time can be sending because the most recent 
         //trgen to send will get the responses until another trgen sends.
         from_addr.sin_addr.s_addr = recv_addr.sin_addr.s_addr;
         from_addr.sin_port = recv_addr.sin_port;
         from_addr.sin_family = recv_addr.sin_family;         
      }
      
      //puts("Forwarded Packet");
   }
   
   free(buffer);
   
   return 0;
}
